<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>sku</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>

    <style type="text/css">
        /* 保存时如果没有验证通过, 要触发浏览器滚动, 这里是设置table表格中的 销售价格 最终渲染的label样式 */
        
        label[for*='table_data'] {
            visibility: hidden;
            margin-top: -40px;
        }
    </style>
</head>

<body>
    <div id="app">
        <el-form ref="form" :rules="rules" :model="form">
            <el-form-item label="添加规格" prop="sku_arr">
                <div style="display: flex;">
                    <div>
                        <el-button v-for="(item, idx) in default_attr" :key="idx" :disabled="attrBtnDisabled" @click="clickDefaultAttr(item)">{{item}}</el-button>
                    </div>

                    <el-popover placement="top" width="240" v-model="add_popover_bool" @after-enter="$refs.addValueInput.focus()">
                        <div style="display: flex; grid-gap: 10px;">
                            <el-input ref="addValueInput" v-model.trim="add_value" @keyup.enter.native="confirm()" />
                            <el-button type="primary" @click="confirm()">确定</el-button>
                        </div>

                        <el-button slot="reference" size="small" type="primary" :disabled="attrBtnDisabled" style="margin-left: 40px;">自定义</el-button>
                    </el-popover>


                    <el-button type="primary" @click="onSubmit()" style="margin-left: 100px;">提交</el-button>
                </div>
            </el-form-item>

            <!-- 规格列表 和 表格 -->
            <section style="margin: 0 0 20px 50px;">
                <!-- 展示已经选择的 -->
                <div v-for="(item, index) in form.sku_arr" :key="index" style="margin-top: 10px;">

                    <!-- 属性 -->
                    <div>
                        <el-input v-model.trim="item.attr" placeholder="属性" style="width: 120px;" @focus="attrFocus(item.attr)" @blur="attrBlur(item.attr)"></el-input>
                        <el-button type="danger" size="mini" icon="el-icon-delete" circle @click="deleteAttr(index)"></el-button>
                    </div>

                    <!-- 属性值 -->
                    <div style="display: flex; margin-top: 10px;">
                        <div v-for="(ktem, kndex) in item.valueList" :key="kndex" style="margin-right: 20px;">
                            <el-input size="small" ref="attrValueInput" v-model.trim="item.valueList[kndex]" placeholder="值" style="width: 100px;" @focus="attrValueFocus(item.valueList[kndex])" @blur="newAttrValueBlur(item.attr, item.valueList[kndex])"></el-input>

                            <el-button v-if="item.valueList.length > 1" type="danger" size="mini" icon="el-icon-delete" circle @click="deleteSmall(index, kndex, item.attr, item.valueList[kndex])" />
                        </div>

                        <el-button type="primary" size="mini" :disabled="item.valueList.includes('')" icon="el-icon-plus" @click="addAttributeValue(index)">添加值</el-button>
                    </div>
                </div>

                <el-table :data="form.table_data" :span-method="spanMethod" style="margin-top: 20px;" border>
                    <template v-for="item_column in table_column">

							<el-table-column v-if="item_column == '图片'" :key="item_column" min-width="150" width="170" align="center" :resizable="false" label="图片">
								<template slot-scope="{ row, $index }">
									<!-- <el-form-item :prop="`table_data.${ $index }.图片`" :rules="rules.sku_img" label-width="0"> -->
									图片组件
									<!-- </el-form-item> -->
								</template>
                    </el-table-column>

                    <!-- 销售价格 使用表单验证 和 自定义表头 -->
                    <el-table-column v-else-if="item_column == '销售价格'" :key="item_column" align="center" :resizable="false">
                        <!-- 自定义表头 -->
                        <template slot="header">
									<div><span style="color: #ff5353;">*</span>销售价格</div>
								</template>

                        <template slot-scope="{ row, $index }">
									<el-form-item :prop="`table_data.${$index}.销售价格`" :rules="rules.sku_sale_price" label-width="0" label=" ">
										<el-input v-model="row[item_column]" :placeholder="item_column" />
									</el-form-item>
								</template>
                    </el-table-column>

                    <!-- 市场价格 -->
                    <el-table-column v-else-if="item_column == '市场价格'" :key="item_column" align="center" :resizable="false" :label="item_column">
                        <template slot-scope="{ row }">
									<div style="height: 62px;">
										<el-input v-model="row[item_column]" :placeholder="item_column" />
									</div>
								</template>
                    </el-table-column>

                    <!-- 库存 -->
                    <el-table-column v-else-if="item_column == '库存'" :key="item_column" align="center" :resizable="false" :label="item_column">
                        <template slot-scope="{ row, $index }">
									<div style="height: 62px;">
										<el-input v-model="row[item_column]" :placeholder="item_column" />
									</div>
								</template>
                    </el-table-column>


                    <!-- 其他属性列 -->
                    <el-table-column v-else align="center" :resizable="false" :key="item_column" :prop="item_column" :label="item_column" />
                    </template>
                </el-table>
            </section>
        </el-form>
    </div>
    <script>
        window.onload = () => {

            let attr_name_value = new Map([ // Map 数据结构, 根据属性名获取对应属性值 返回数组
                ['颜色', ['黑', '白', '红']],
                ['尺寸', ['大', '中', '小']],
                ['重量', ['500g', '1kg', '5kg']]
            ])
            let base_column = ['销售价格', '市场价格', '库存'] // 基本的列

            let first_column_rule = [] // 第一列与第二列使用相同的合并规则 (不能存在data中)
            let old_attr = '' // 每次当属性获得焦点时都会获取输入框内的值，保存于此
            let old_attr_value = '' // 每次当属性值获得焦点时都会获取输入框内的值，保存于此

            new Vue({
                el: '#app',
                computed: {
                    // 已添加的属性(字符串数组)
                    selectedAttr() {
                        return this.form.sku_arr.map(x => x.attr)
                    },
                    // 是否可以添加属性 最多两个属性
                    attrBtnDisabled() {
                        return false
                        return this.form.sku_arr.length >= 2
                    }
                },
                data: {
                    default_attr: ['颜色', '尺寸', '重量'], // 默认规格
                    table_column: base_column, // 表格列
                    add_popover_bool: false, // 添加属性的小弹窗
                    add_value: '', // 添加属性的
                    // 上边的数据是录入sku相关

                    // 表单
                    form: {
                        sku_arr: [],
                        table_data: [], // 表格中的数据
                    },

                    // 验证规则
                    rules: {
                        // sku 相关验证
                        sku_arr: {
                            validator: (rule, value, callback) => {
                                if (value.length === 0) return callback(new Error('请添加规格'))
                                else return callback()
                            },
                            trigger: 'blur'
                        },
                        sku_img: [{
                            required: true,
                            message: '图片不能为空',
                            trigger: 'blur'
                        }, {
                            type: 'string',
                            message: '请等待图片上传完毕',
                            trigger: 'blur'
                        }, ],
                        sku_sale_price: {
                            required: true,
                            message: '价格不能为空',
                            trigger: 'blur'
                        }
                    }
                },

                methods: {
                    // 点击默认的规格按钮
                    clickDefaultAttr(attr_name) {
                        if (this.selectedAttr.includes(attr_name)) return
                        this.form.sku_arr.push({
                                attr: attr_name,
                                valueList: [...attr_name_value.get(attr_name)]
                            }) //解决引用类型导致的问题

                        this.generateTableColumn()
                        this.traverseSku() // 处理SKU， 生成表格

                        console.log(this.form.sku_arr)
                    },
                    // 点击自定义里的确定 添加新的规格
                    confirm() {
                        if (!this.add_value) return
                        this.form.sku_arr.push({
                            attr: this.add_value,
                            valueList: ['']
                        })

                        this.generateTableColumn()
                        this.traverseSku()

                        this.add_popover_bool = false
                        this.add_value = ''
                    },
                    // 属性获得焦点时 得到旧的值 存一下
                    attrFocus(oldVal) {
                        old_attr = oldVal
                    },
                    // 属性失去焦点时
                    attrBlur(newVal) {
                        console.log('attrBlur')
                            // 如果 '新值等于旧值' 或者 '空' 什么也不做
                        if (newVal === old_attr || newVal === '') return

                        // 生成处理表头数据和表格数据
                        this.generateTableColumn()
                        this.traverseSku()
                    },
                    // 删除属性
                    deleteAttr(idx) {
                        this.form.sku_arr.splice(idx, 1)
                            // 生成处理表头数据和表格数据
                        this.generateTableColumn()
                        this.traverseSku()
                    },


                    // 添加属性值
                    async addAttributeValue(idx) {
                        this.form.sku_arr[idx].valueList.push('')
                            // 让新增的输入框获得焦点
                        await this.$nextTick()
                        this.$refs.attrValueInput[this.$refs.attrValueInput.length - 1].focus()
                    },
                    // 属性值获得焦点时 得到旧的值 在输入框失去焦点的时候, 如果值没有变化, 则什么也不做
                    attrValueFocus(oldVal) {
                        old_attr_value = oldVal
                    },
                    // 属性值失去焦点时, 操作表格数据 (新版本 可以实现无限个规格)
                    newAttrValueBlur(curr_attr, newVal) {
                        if (newVal === old_attr_value) return

                        //  这里根据规格生成的笛卡尔积计算出table中需要改变的行索引 ( 包含新增和修改 )
                        let cartesian_arr = this.generateBaseData(this.form.sku_arr)
                        console.log(cartesian_arr)
                        let change_idx_arr = [] // 需要被改变的行的索引
                        for (let i in cartesian_arr) {
                            if (cartesian_arr[i][curr_attr] === newVal) change_idx_arr.push(Number(i))
                        }
                        console.log('change_idx_arr', change_idx_arr)

                        // 新的表格应该有的长度与现有的表格长度比较, 区分新增还是修改
                        let length_arr = this.form.sku_arr.map(x => x.valueList.length)
                        let new_table_length = length_arr.reduce((acc, curr_item) => acc * curr_item) // 新的表格数据长度 求乘积
                        let old_table_length = this.form.table_data.length // 旧的表格数据长度

                        // 如果是修改
                        if (new_table_length === old_table_length) {
                            this.form.table_data.forEach((item, index) => {
                                if (change_idx_arr.includes(index)) this.form.table_data[index][curr_attr] = newVal
                            })
                            return
                        }
                        // 如果是新增
                        if (new_table_length > old_table_length) {
                            // 得到当前属性的当前值和其他规格的 sku_arr, 构造新的表格数据
                            let other_sku_arr = this.form.sku_arr.map(item => {
                                    if (item.attr !== curr_attr) return item
                                    else return {
                                        attr: item.attr,
                                        valueList: [newVal]
                                    }
                                })
                                // 得到新增的表格数据
                            let ready_map = this.generateBaseData(other_sku_arr)
                            let new_table_data = this.mergeTableData(ready_map)
                            change_idx_arr.forEach((item_idx, index) => {
                                this.form.table_data.splice(item_idx, 0, new_table_data[index])
                            })
                        }
                    },
                    // 删除属性值 四个参数：'一级数组索引', '二级索引', '属性名字', '属性值'  后两个参数用来删除行
                    deleteSmall(idx, kdx, attr_name, attr_val) {
                        this.form.sku_arr[idx].valueList.splice(kdx, 1)

                        // 删除table行
                        let data_length = this.form.table_data.length
                        for (let i = 0; i < data_length; i++) {
                            if (this.form.table_data[i][attr_name] == attr_val) {
                                this.form.table_data.splice(i, 1)
                                i = i - 1
                                data_length = data_length - 1
                            }
                        }
                    },

                    // 根据 `this.form.sku_arr` 生成表格列, `table_column` 用于 el-table-column 的 v-for
                    async generateTableColumn() {
                        this.table_column = this.form.sku_arr.map(x => x.attr).concat(base_column)
                            /*
                            	不写 `$nextTick`会有bug, 没想明白为啥, 大概是vue懒得更新dom吧
                            	bug复现方式: 删除`$nextTick`后，勾选颜色，再勾选尺寸，再取消勾选颜色，观察el-table
                            */
                        await this.$nextTick()
                        if (this.form.sku_arr.length != 0) this.table_column.splice(1, 0, '图片')
                    },

                    // 合并行
                    spanMethod({
                        row,
                        column,
                        rowIndex,
                        columnIndex
                    }) {
                        if (columnIndex == 0) {
                            let key_0 = column.label
                            let first_idx = this.form.table_data.findIndex(x => x[key_0] == row[key_0])
                            const calcSameLength = () => this.form.table_data.filter(x => x[key_0] == row[key_0]).length
                            first_column_rule = rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
                            return first_column_rule

                            // 第二列的图片与第一列主规格使用相同合并规则 ( 恰好el-table的合并方法是横着走的 )
                        } else if (columnIndex == 1) {
                            return first_column_rule

                            // 其他列
                        } else {
                            // 表格数据的每一项, 
                            const callBacks = (table_item, start_idx = 0) => {
                                if (columnIndex < start_idx) return true
                                let curr_key = this.table_column[start_idx]
                                return table_item[curr_key] === row[curr_key] && callBacks(table_item, ++start_idx)
                            }
                            let first_idx = this.form.table_data.findIndex(x => callBacks(x))
                            const calcSameLength = () => this.form.table_data.filter(x => callBacks(x)).length
                            return rowIndex == first_idx ? [calcSameLength(), 1] : [0, 0]
                        }
                    },
                    // 合并 sku 与 '图片', '销售价格', '库存', '市场价格' , 返回整个表格数据数组
                    mergeTableData(arr) {
                        return arr.map(item => ({...item,
                            '销售价格': '',
                            '市场价格': '',
                            '库存': '',
                            '图片': ''
                        }))
                    },
                    // 遍历 `sku_arr` 生成表格数据
                    traverseSku() {
                        let ready_map = this.generateBaseData(this.form.sku_arr)
                        this.form.table_data = this.mergeTableData(ready_map)
                    },
                    // 重新实现笛卡尔积  入参是: this.form.sku_arr 传入的数组 '为空', '长度为1', '长度大于1' 三种情况 分别处理
                    generateBaseData(arr) {
                        if (arr.length === 0) return []
                        if (arr.length === 1) {
                            let [item_spec] = arr
                            return item_spec.valueList.map(x => {
                                return {
                                    [item_spec.attr]: x
                                }
                            })
                        }
                        if (arr.length >= 1) {
                            return arr.reduce((accumulator, spec_item) => {
                                let acc_value_list = Array.isArray(accumulator.valueList) ? accumulator.valueList : accumulator
                                let item_value_list = spec_item.valueList
                                let result = []
                                for (let i in acc_value_list) {
                                    for (let j in item_value_list) {
                                        let temp_data = {}
                                            // 如果是对象
                                        if (acc_value_list[i].constructor === Object) {
                                            temp_data = {
                                                ...acc_value_list[i],
                                                [spec_item.attr]: item_value_list[j]
                                            }

                                            // 否则如果是字符串
                                        } else {
                                            temp_data[accumulator.attr] = acc_value_list[i]
                                            temp_data[spec_item.attr] = item_value_list[j]
                                        }
                                        result.push(temp_data)
                                    }
                                }
                                return result
                            })
                        }
                    },

                    onSubmit() {
                        this.$refs.form.validate(async(valid, object) => {
                            if (!valid) {
                                // 获取元素距离body顶部的距离
                                let getTop = dom => {
                                    let top = dom.offsetTop
                                        // while括号里的值是 dom.offsetParent
                                    while (dom = dom.offsetParent) top = top + dom.offsetTop
                                    return top
                                }
                                let [first_prop] = Object.keys(object)
                                let top = getTop(document.querySelector(`label[for='${first_prop}']`))
                                window.scrollTo({
                                    top: top - 70,
                                    behavior: 'smooth'
                                })

                                return
                            }

                            this.btn_loading = true

                        })
                    }
                }
            })
        }
    </script>
</body>

</html>